//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#include "CProgram.h"

namespace Noise
{
	
	// constructor
	CProgram::CProgram () :
		_options( EBuildOptions(0) ), _format( EPixelFormat(0) ), _enableViewer(false),
		_coordScale(1), _coordBias(0)
	{
	}

	// destructor
	CProgram::~CProgram ()
	{
	}

	// Delete
	void CProgram::Delete ()
	{
		_srcData = CSource::TFunctionData();
		_options = EBuildOptions(0);
	}
	
	// _PreBuild
	bool CProgram::_PreBuild (CSourcePtr source, CSource::TPreBuildData &data) const
	{
		data.Clear();
		
		int status = 0;

		source->GetInteger( SOURCE_STATE, &status, 1 );
		CHECK_VALUE( status == SOURCE_COMPLETE );

		data.Push( source.ptr() );
		source->PreBuild( data );

		usize	processed	= 1;	// next index to process
		bool	res			= true;

		for (;;)
		{
			const usize  count = data.Sources().Count();

			if ( processed >= count )
				break;

			for (; processed < count; ++processed)
			{
				data.Sources()[ processed ]->GetInteger( SOURCE_STATE, &status, 1 );
				CHECK_VALUE( status == SOURCE_COMPLETE );

				data.Sources()[ processed ]->PreBuild( data );
			}
		}

		return res;
	}

	// _Build
	bool CProgram::_Build (const CSource::TPreBuildData &data, CSource::TFunctionData &funcData) const
	{

		FOR_rev( i, data.Sources() )
		{
			CHECK_RETURN( data.Sources()[i]->BuildFunction( funcData ) );
			funcData.source << '\n';
		}

		return true;
	}

	// Build
	EResult CProgram::Build (CSourcePtr source, EBuildOptions options)
	{
		_options = options;

		CSource::TPreBuildData	preBuildData;
		CHECK_RETURN_R( _PreBuild( source, preBuildData ), INVALID_OPERATION );
		CHECK_RETURN_R( _Build( preBuildData, _srcData ), INVALID_OPERATION );

		return OK;
	}
	
	// SetInteger
	EResult CProgram::SetInteger (EProgramParameter pname, const int *v, int count)
	{
		CHECK_RETURN_R( count >= 1, INVALID_VALUE );

		switch ( pname )
		{
			case SHOW_IMAGE_PREVIEW :
				_enableViewer = !!v[0];
				return OK;
		}

		return INVALID_ENUM;
	}

	// SetReal
	EResult CProgram::SetReal (EProgramParameter pname, const real *v, int count)
	{
		CHECK_RETURN_R( count >= 6, INVALID_VALUE );

		switch ( pname )
		{
			case PROGRAM_COORD_BOUNDS :
				_coordScale = vec3( v[1] - v[0], v[3] - v[2], v[5] - v[4] );
				_coordBias  = vec3( v[0], v[2], v[4] );
				return OK;
		}

		return INVALID_ENUM;
	}

	// GetInteger
	EResult CProgram::GetInteger (EProgramQuery pname, int *v, int count) const
	{
		CHECK_RETURN_R( count >= 1, INVALID_VALUE );

		switch ( pname )
		{
			case PROGRAM_SHOW_IMAGE_PREVIEW :
				v[0] = _enableViewer;
				return OK;
		}

		return INVALID_ENUM;
	}

	// GetFloat
	EResult CProgram::GetReal (EProgramQuery pname, real *v, int count) const
	{
		CHECK_RETURN_R( count >= 6, INVALID_VALUE );

		switch ( pname )
		{
			case PROGRAM_COORD_BOUNDS :
				v[0] = _coordBias[0];
				v[1] = _coordBias[0] + _coordScale[0];
				v[2] = _coordBias[1];
				v[3] = _coordBias[1] + _coordScale[1];
				v[4] = _coordBias[2];
				v[5] = _coordBias[2] + _coordScale[2];
				return OK;
		}

		return INVALID_ENUM;
	}

	// GetString
	EResult CProgram::GetString (EProgramQuery pname, char *buf, int length) const
	{
		return INVALID_ENUM;
	}
	
	// RenderToImage
	EResult CProgram::RenderToImage (const uvec3 &dim, EPixelFormat format, EPixelType type,
									 EImageType imageType, const char *filename)
	{
		const uint		bpp = _GetBytePerPixel( format, type );
		buffer<uint8>	image( dim.x * dim.y * dim.z * bpp );

		EResult res = RenderToMemory( dim, uvec2(0), format, type, image.ptr() );

		if ( res != OK )
			return res;

		switch ( imageType )
		{
			case BMP :
				_SaveBitmap( dim.xy(), image, filename );
				break;

			case BMP_PACKED_FLOAT24 :
				_SaveBitmapPackedFloat( dim.xy(), uni_array<float>( (float *)image.ptr(), image.Size()/sizeof(float) ), filename );
				break;

			case CUSTOM_FLOAT :
				_SaveBitmapCustomFloat( dim, image, filename );
				break;

			default :
				return INVALID_ENUM;
		}

		return OK;
	}
	
	// _s_GetBytePerPixel
	uint CProgram::_GetBytePerPixel (EPixelFormat format, EPixelType type)
	{
		struct PixelFormatInfo {
			EPixelType	type;
			uint		channels;
			uint		defChannels;
			uint		bpp;
		};

		const uint ALL_CHANNELS	= -1;

		static const PixelFormatInfo	s_aFormats[] = {
			{ SINT8,				ALL_CHANNELS,		1,	sizeof(int8) },
			{ SINT16,				ALL_CHANNELS,		1,	sizeof(int16) },
			{ SINT32,				ALL_CHANNELS,		1,	sizeof(int32) },
			{ UINT8,				ALL_CHANNELS,		1,	sizeof(uint8) },
			{ UINT16,				ALL_CHANNELS,		1,	sizeof(uint16) },
			{ UINT32,				ALL_CHANNELS,		1,	sizeof(uint32) },
			{ FLOAT16,				ALL_CHANNELS,		1,	sizeof(half) },
			{ FLOAT32,				ALL_CHANNELS,		1,	sizeof(float) },
			//{ FLOAT64_UINT32X2,		(1<<R) | (1<<RG),	1,	sizeof(double) },
			{ FLOAT64,				ALL_CHANNELS,		1,	sizeof(double) },
			{ SNORM_8,				ALL_CHANNELS,		1,	sizeof(int8) },
			{ SNORM_16,				ALL_CHANNELS,		1,	sizeof(int16) },
			{ UNORM_8,				ALL_CHANNELS,		1,	sizeof(uint8) },
			{ UNORM_16,				ALL_CHANNELS,		1,	sizeof(uint16) },
			{ UNORM_5_6_5,			1 << RGB,			3,	sizeof(uint16) },
			{ UNORM_10_10_10,		1 << RGB,			3,	sizeof(uint32) }
		};

		STATIC_ASSERTE( R == 1 && RG == 2 && RGB == 3 && RGBA == 4 );


		for (uint i = 0; i < count_of(s_aFormats); ++i)
		{
			if ( type == s_aFormats[i].type )
			{
				if ( EnumNoCmp( s_aFormats[i].channels, 1 << format ) )
					continue;

				return s_aFormats[i].bpp * format / s_aFormats[i].defChannels;
			}
		}
		return 0;
	}
	
	// _OpenFile
	inline bool CProgram::_OpenFile (const char *filename, FILE *&file)
	{
		file = fopen( filename, "wb" );
		CHECK_ERROR( file != null, "Can't open or create file: \"" << filename << "\"" );
		return true;
	}

	// _SaveBitmap
	void CProgram::_SaveBitmap (const uvec2 &dim, const uni_array<const uint8> &data, const char *filename)
	{
#		pragma pack(push, 1)
		struct TBitmapFileHeader	//BITMAPFILEHEADER
		{
			uint16	bfType;
			uint32	bfSize;
			uint16	bfReserved1;
			uint16	bfReserved2;
			uint32	bfOffBits;
		};

		struct TBitmapInfoHeader	//BITMAPINFOHEADER
		{
			uint32	biSize;
			int32	biWidth;
			int32	biHeight;
			uint16	biPlanes;
			uint16	biBitCount;
			uint32	biCompression;
			uint32	biSizeImage;
			int32	biXPelsPerMeter;
			int32	biYPelsPerMeter;
			uint32	biClrUsed;
			uint32	biClrImportant; 
		};
#		pragma pack(pop)


		FILE * file = null;
		CHECK_RETURN_R( _OpenFile( filename, file ), );

		TBitmapFileHeader	s_file_header = {0};
		TBitmapInfoHeader	s_bmp_header  = {0};
		bool				b_write		  = true;
		
		s_file_header.bfType		= 0x4d42;
		s_file_header.bfSize		= 0;
		s_file_header.bfReserved1	= 0;
		s_file_header.bfReserved2	= 0;
		s_file_header.bfOffBits		= sizeof(TBitmapFileHeader) + sizeof(TBitmapInfoHeader);
		
		s_bmp_header.biSize			= sizeof(TBitmapInfoHeader);
		s_bmp_header.biWidth		= dim.x;
		s_bmp_header.biHeight		= dim.y;
		s_bmp_header.biPlanes		= 1;
		s_bmp_header.biBitCount		= 24;
		s_bmp_header.biCompression	= 0;	// BI_RGB
		s_bmp_header.biSizeImage	= (uint)data.Size();
		s_bmp_header.biXPelsPerMeter = 0;
		s_bmp_header.biYPelsPerMeter = 0;
		s_bmp_header.biClrUsed		= 0;
		s_bmp_header.biClrImportant	= 0;

		fwrite( &s_file_header,	sizeof(s_file_header),	1, file );
		fwrite( &s_bmp_header,	sizeof(s_bmp_header),	1, file );
		fwrite( data.ptr(),		data.Size(),			1, file );

		fclose( file );
	}

	// _SaveBitmapPackedFloat
	void CProgram::_SaveBitmapPackedFloat (const uvec2 &dim, const uni_array<float> &data, const char *filename)
	{
#	pragma pack(push, 1)
		struct RGB8
		{
			uint8	r,
					g,
					b;
			///
			RGB8 (float value)
			{
				vec3 v = Fract( vec3( 1.0f, 255.0f, 65025.0f ) * Clamp( value, 0.0f, 1.0f ) );

				v -= vec3(v.y, v.z, v.z) * vec3( 1.0f / 255.0f );

				r = uint8(v.x * 255.0f + 0.5f);
				g = uint8(v.y * 255.0f + 0.5f);
				b = uint8(v.z * 255.0f + 0.5f);
			}
			
			operator float () const
			{
				return Dot( vec3(float(r), float(g), float(b) ), vec3( 1.0f, 1.0f/255.0f, 1.0f/65025.0f ) / 255.0f );
			}
		};
#	pragma pack(pop)

		const float	*	src = data.ptr();
		RGB8	*		dst = (RGB8 *)data.ptr();

		for (uint y = 0; y < dim.y; ++y)
		{
			for (uint x = 0; x < dim.x; ++x)
			{
				int i = x + y * dim.x;

				dst[i] = src[i];
			}
		}

		return _SaveBitmap( dim, uni_array<const uint8>( (uint8 *)dst, dim.x*dim.y*3/4 ), filename );
	}
	
	// _SaveBitmapCustomFloat
	void CProgram::_SaveBitmapCustomFloat (const uvec3 &dim, const uni_array<const uint8> &data, const char *filename)
	{

	}

}	// Noise
